package cn.wenxin_zhang.smart4j.plus.proxy;

import cn.wenxin_zhang.smart4j.plus.annotation.Delete;
import cn.wenxin_zhang.smart4j.plus.annotation.Insert;
import cn.wenxin_zhang.smart4j.plus.annotation.Select;
import cn.wenxin_zhang.smart4j.plus.annotation.Update;
import cn.wenxin_zhang.smart4j.plus.utils.DBUtils;
import net.sf.cglib.proxy.MethodProxy;
import org.apache.commons.dbutils.DbUtils;
import org.apache.commons.dbutils.ResultSetHandler;
import org.apache.commons.dbutils.handlers.BeanHandler;
import org.apache.commons.dbutils.handlers.BeanListHandler;
import org.apache.commons.lang.ArrayUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.smart4j.framework.proxy.Proxy;
import org.smart4j.framework.proxy.ProxyChain;

import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.List;

/**
 * Created by Wenxin on 2017/3/8.
 */
public class MapperProxy implements Proxy {

    private final Logger logger;
    private final Object obj;

    private final Class<?> targetClass;
    private final Method method;
    private final Object[] params;
    private final MethodProxy methodProxy;

    private Connection conn;

    public MapperProxy(Object obj, Class<?> targetClass, Method method, Object[] params, MethodProxy methodProxy) {
        this.obj = obj;
        this.targetClass = targetClass;
        this.method = method;
        this.params = params;
        this.methodProxy = methodProxy;
        logger = LoggerFactory.getLogger(targetClass);
    }

    @Override
    public Object doProxy(ProxyChain proxyChain) throws Throwable {

        Object result = null;
        try {
//          begin打印日志
            begin(targetClass, method, params);
//            before获取连接
            before(targetClass, method, params);
//            是否有mapper类注解
            if (intercept(targetClass, method, params)) {
//                获得select注解
                Select select = method.getAnnotation(Select.class);
                if (select != null) {
//                    查询
                    String sql = select.value();
//                  带ResultSet处理器的查询
                    if (params != null && params.length > 0 && params[0] instanceof ResultSetHandler) {
                        Object[] queryParams = ArrayUtils.subarray(params, 1, params.length);
                        logStat(sql, queryParams);
                        result = DBUtils.query(conn, sql, (ResultSetHandler) params[0], queryParams);
                        return result;
                    } else {
                        logStat(sql, params);
                        Class<?> returnType = method.getReturnType();
//                    返回List
                        if (ArrayUtils.contains(returnType.getInterfaces(), List.class) || returnType.equals(List.class)) {
                            ParameterizedType parameterizedType = (ParameterizedType) method.getGenericReturnType();
                            result = DBUtils.query(conn, sql, new BeanListHandler(Class.forName(parameterizedType.getActualTypeArguments()[0].getTypeName())), params);
                        } else {
//                        返回bean处理
                            result = DBUtils.query(conn, sql, new BeanHandler(returnType), params);
                        }
                        afterReturn(targetClass, method, params, result);
                        return result;
                    }
                }

//                获得修改注解
                Update update = method.getAnnotation(Update.class);
                if (update != null) {
//                    执行修改
                    String sql = update.value();
                    logStat(sql, params);
                    result = DBUtils.update(conn, sql, params);
                    afterReturn(targetClass, method, params, result);
                    return result;
                }

//                获得删除注解
                Delete delete = method.getAnnotation(Delete.class);
                if (delete != null) {
//                    执行删除
                    String sql = delete.value();
                    logStat(sql, params);
                    result = DBUtils.delete(conn, sql, params);
                    afterReturn(targetClass, method, params, result);
                    return result;
                }

//              获得插入注解
                Insert insert = method.getAnnotation(Insert.class);
                if (insert != null) {
//                  执行插入
                    String sql = insert.value();
                    logStat(sql, params);
                    if (params instanceof Object[])
                        result = DBUtils.insert(conn, sql, params);
                    if (params instanceof Object[][])
                        result = DBUtils.insertBatch(conn, sql, (Object[][]) params);
                    afterReturn(targetClass, method, params, result);
                    return result;
                }
//            无注解或注解方式不对，抛出异常
                throw new RuntimeException("mapper annotation error");
            } else {
//                不是注解方法
                result = methodProxy.invokeSuper(obj, params);
                return result;
            }
        } catch (Exception e) {
            error(targetClass, method, params, e);
            logger.error("mapper proxy exception:", e);
            throw e;
        } finally {
//            after方法关闭连接
            after(targetClass, method, params);
//            end方法打印日志
            end(targetClass, method, params);
        }
    }

    private void afterReturn(Class<?> targetClass, Method method, Object[] params, Object result) {
        logger.debug("result: {}", result);
    }

    public boolean intercept(Class<?> cls, Method method, Object[] params) throws Throwable {
        Annotation[] declaredAnnotations = method.getDeclaredAnnotations();
        for (Annotation annotation : declaredAnnotations) {
            boolean hasMapperAnnotation = annotation instanceof Select || annotation instanceof Update || annotation instanceof Delete || annotation instanceof Insert;
            if (hasMapperAnnotation)
                return hasMapperAnnotation;
        }
        return false;
    }

    private void end(Class<?> targetClass, Method method, Object[] params) {
        if (logger.isDebugEnabled()) {
            logger.debug("-------------------------------mapper proxy end-----------------------------------------");
        }
    }

    private void after(Class<?> targetClass, Method method, Object[] params) {
        try {
            boolean isTrasaction = !conn.getAutoCommit();
            if (!isTrasaction) {
                logger.debug("{} close conncetion!", MapperProxy.class);
                DBUtils.clossConnection(DBUtils.getConnection());
                conn = null;
            }
        } catch (Exception e) {
            logger.debug("get auto commit or close connection failure", e);
            throw new RuntimeException(e);
        }
    }

    private void error(Class<?> targetClass, Method method, Object[] params, Exception e) {
    }

    private void before(Class<?> targetClass, Method method, Object[] params) {
        conn = DBUtils.getConnection();
    }

    private void begin(Class<?> targetClass, Method method, Object[] params) {
        if (logger.isDebugEnabled()) {
            logger.debug("-------------------------------mapper proxy begin---------------------------------------");
            logger.debug("method:{}.{}", targetClass.getName(), method.getName());
        }
    }

    private void logStat(String sql, Object[] params) {
        logger.debug("Statement: {}", sql);
        logger.debug("Params: {}", ArrayUtils.toString(params));
    }
}
